﻿/****************************************************************************************
 * Name         :   Image processing filters
 * Author       :   Shah-nawaj Shuhan
 * Description  :   This file is part of Onuprova 3D camera. It impliments some filters
 *                  to generate stereoscopic image.
 * Company      :   Onuprova
 * URL          :   http://onuprova.com
 * 
 * Reference    :   Information from http://en.wikipedia.org/wiki/Anaglyph_image
 *                  has been used to generate the filter configuration.
 ****************************************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Drawing;
using System.Drawing.Imaging;

namespace Onuprova.Anaglyphing.ImageProcessor
{

    /// <summary>
    /// Event handler to handle the change of Adjustment parameter
    /// </summary>
    /// <param name="NewParameter"></param>
    public delegate void AdjustmentParameterChangedEventHandler(AdjustmentParameter NewParameter);

    /// <summary>
    /// Enumarate the colour schemes
    /// </summary>
    public enum ColourScheme
    {
        RedGreen = 0,
        RedBlue = 1,
        RedCyan = 2,
        GreenMagenta = 3,
        MagentaCyan = 4
    }

    /// <summary>
    /// Configuration for colour filter
    /// </summary>
    public class ColourFilterConfiguration
    {
        /// <summary>
        /// Percentage of red channel
        /// </summary>
        public float Red { get; set; }
        /// <summary>
        /// Percentage of green channel
        /// </summary>
        public float Green { get; set; }
        /// <summary>
        /// Percentage of blue channel
        /// </summary>
        public float Blue { get; set; }

        /// <summary>
        /// Create a new instance of colour filter configuration
        /// </summary>
        /// <param name="Red">Percentage of red (0-100)</param>
        /// <param name="Green">Percentage of green (0-100)</param>
        /// <param name="Blue">Percentage of blue (0-100)</param>
        public ColourFilterConfiguration(float Red, float Green, float Blue)
        {
            if (Red > 1 || Green > 1 || Blue > 1 || Red < 0 || Green < 0 || Blue < 0) throw (new Exception("Red green and Blue must be between 0-1"));
            this.Red = Red;
            this.Green = Green;
            this.Blue = Blue;
        }

        /// <summary>
        /// Overload constractor without any parameter
        /// </summary>
        public ColourFilterConfiguration()
        {

        }
    }

    /// <summary>
    /// Transform factor class for Image transformation
    /// </summary>
    public class TransformFactor
    {
        /// <summary>
        /// Left shift in pixel
        /// </summary>
        public int LeftShift { get; set; }
        /// <summary>
        /// Top shift in pixel
        /// </summary>
        public int TopShift { get; set; }
        /// <summary>
        /// Rotation angle in degree
        /// </summary>
        public float RotationAngle { get; set; }
        /// <summary>
        /// Zoom factor (CurrentSize/PreviousSize)
        /// </summary>
        public float ZoomFactor { get; set; }

    }

    /// <summary>
    /// Image adjustment parameter class
    /// </summary>
    public class AdjustmentParameter
    {
        /// <summary>
        /// Colour filter configuration to use for channel manipulation
        /// </summary>
        public ColourFilterConfiguration ColourFilter { get; set; }
        /// <summary>
        /// Transformation factor to use to adjust image
        /// </summary>
        public TransformFactor Transformation { get; set; }
    }

    /// <summary>
    /// Configuration for anaglyph imaging
    /// </summary>
    public class AnaglyphConfiguration
    {
        /// <summary>
        /// Adjustment parameter for left image
        /// </summary>
        public AdjustmentParameter LeftCalibrationInfo { get; set; }
        /// <summary>
        /// Adjustment parameter for right image
        /// </summary>
        public AdjustmentParameter RightCalibrationInfo { get; set; }
        /// <summary>
        /// Is to swap between left and right image
        /// </summary>
        public bool IsSwap { get; set; }
        /// <summary>
        /// Viewport of the generated image
        /// </summary>
        public Rectangle ViewPort { get; set; }
    }

    /// <summary>
    /// Contains some filtering function for image processing
    /// </summary>
    public class ImageProcessor
    {
        /// <summary>
        /// Compute anaglyph image for the given images and colour filter configuration
        /// </summary>
        /// <param name="LeftImage">Image captured by the left camera</param>
        /// <param name="LeftFilter">Colour configuration for left image</param>
        /// <param name="RightImage">Image captured by right camera</param>
        /// <param name="RightFilter">Colour configuration for right image</param>
        /// <returns>Anaglyph image</returns>
        public static Bitmap GetAnaglyph(Bitmap LeftImage, ColourFilterConfiguration LeftFilter, Bitmap RightImage, ColourFilterConfiguration RightFilter)
        {
            if (LeftImage.Width != RightImage.Width || LeftImage.Height != RightImage.Height)
            {
                throw new Exception("Left and Right images should have same width and height");
            }
            if (LeftFilter.Red + RightFilter.Red > 1 || LeftFilter.Green + RightFilter.Green > 1 || LeftFilter.Blue + RightFilter.Blue > 1)
            {
                throw new Exception("Filter parameter has been conflicted. Summation each colour percentages in the Left and Right filter should be less equal 1. Which means LeftFilter.Red + RightFilter.Red <= 1. It also applies for Green and Blue channels");
            }

            UnsafeBitmap OutputBitmap = new UnsafeBitmap(LeftImage.Width, LeftImage.Height);
            UnsafeBitmap LeftUnsafe = new UnsafeBitmap(LeftImage);
            UnsafeBitmap RightUnsafe = new UnsafeBitmap(RightImage);
            OutputBitmap.LockBitmap();
            LeftUnsafe.LockBitmap();
            RightUnsafe.LockBitmap();

            for (int w = 0; w < LeftImage.Width; w++)
            {
                for (int h = 0; h < RightImage.Height; h++)
                {
                    Pixel LeftPixel = LeftUnsafe.GetPixel(w, h);
                    Pixel RightPixel = RightUnsafe.GetPixel(w, h);
                    //Calculate Red
                    byte Red = (byte)((float)RightPixel.red * RightFilter.Red + ((float)LeftPixel.red * LeftFilter.Red));
                    //Calculate Green
                    byte Green = (byte)((float)RightPixel.green * RightFilter.Green + ((float)LeftPixel.green * LeftFilter.Green));
                    //Calculate Blue
                    byte Blue = (byte)((float)RightPixel.blue * RightFilter.Blue + ((float)LeftPixel.blue * LeftFilter.Blue));
                    //Set Pixel
                    OutputBitmap.SetPixel(w, h, new Pixel(Red, Green, Blue));
                }
            }

            OutputBitmap.UnlockBitmap();
            LeftUnsafe.UnlockBitmap();
            RightUnsafe.UnlockBitmap();

            return OutputBitmap.Bitmap;
        }

        /// <summary>
        /// Compute anaglyph image for the given images and configuration
        /// </summary>
        /// <param name="LeftImage"></param>
        /// <param name="RightImage"></param>
        /// <param name="Configuration"></param>
        /// <returns></returns>
        public static Bitmap GetAnaglyph(Bitmap LeftImage, Bitmap RightImage, AnaglyphConfiguration Configuration)
        {
            if (LeftImage.Width != RightImage.Width || LeftImage.Height != RightImage.Height)
            {
                throw new Exception("Left and Right images should have same width and height");
            }

            //Colour filter adjustment
            Configuration.LeftCalibrationInfo.ColourFilter.Red = (Configuration.LeftCalibrationInfo.ColourFilter.Red / (Configuration.LeftCalibrationInfo.ColourFilter.Red + Configuration.RightCalibrationInfo.ColourFilter.Red));
            Configuration.LeftCalibrationInfo.ColourFilter.Green = (Configuration.LeftCalibrationInfo.ColourFilter.Green / (Configuration.LeftCalibrationInfo.ColourFilter.Green + Configuration.RightCalibrationInfo.ColourFilter.Green));
            Configuration.LeftCalibrationInfo.ColourFilter.Blue = (Configuration.LeftCalibrationInfo.ColourFilter.Blue / (Configuration.LeftCalibrationInfo.ColourFilter.Blue + Configuration.RightCalibrationInfo.ColourFilter.Blue));

            Configuration.RightCalibrationInfo.ColourFilter.Red = (Configuration.RightCalibrationInfo.ColourFilter.Red / (Configuration.LeftCalibrationInfo.ColourFilter.Red + Configuration.RightCalibrationInfo.ColourFilter.Red));
            Configuration.RightCalibrationInfo.ColourFilter.Green = (Configuration.RightCalibrationInfo.ColourFilter.Green / (Configuration.LeftCalibrationInfo.ColourFilter.Green + Configuration.RightCalibrationInfo.ColourFilter.Green));
            Configuration.RightCalibrationInfo.ColourFilter.Blue = (Configuration.RightCalibrationInfo.ColourFilter.Blue / (Configuration.LeftCalibrationInfo.ColourFilter.Blue + Configuration.RightCalibrationInfo.ColourFilter.Blue));

            //Transform images on here
            Bitmap LeftBmp = new Bitmap(LeftImage.Width, LeftImage.Height);
            Bitmap RightBmp = new Bitmap(RightImage.Width, RightImage.Height);

            Graphics g1 = Graphics.FromImage(LeftBmp);
            g1.TranslateTransform((((Configuration.LeftCalibrationInfo.Transformation.ZoomFactor * LeftImage.Width) / 2) + Configuration.LeftCalibrationInfo.Transformation.LeftShift), (((Configuration.LeftCalibrationInfo.Transformation.ZoomFactor * LeftImage.Height) / 2) + Configuration.LeftCalibrationInfo.Transformation.TopShift));
            g1.RotateTransform(Configuration.LeftCalibrationInfo.Transformation.RotationAngle);
            g1.TranslateTransform(-1 * (((Configuration.LeftCalibrationInfo.Transformation.ZoomFactor * LeftImage.Width) / 2) + Configuration.LeftCalibrationInfo.Transformation.LeftShift), -1 * (((Configuration.LeftCalibrationInfo.Transformation.ZoomFactor * LeftImage.Height) / 2) + Configuration.LeftCalibrationInfo.Transformation.TopShift));
            g1.DrawImage(LeftImage, new Rectangle(Configuration.LeftCalibrationInfo.Transformation.LeftShift, Configuration.LeftCalibrationInfo.Transformation.TopShift, (int)(LeftImage.Width * Configuration.LeftCalibrationInfo.Transformation.ZoomFactor), (int)(LeftImage.Height * Configuration.LeftCalibrationInfo.Transformation.ZoomFactor)), new Rectangle(0, 0, LeftImage.Width, LeftImage.Height), GraphicsUnit.Pixel);
            g1.Flush();
            g1.Dispose();

            Graphics g2 = Graphics.FromImage(RightBmp);
            g2.TranslateTransform((((Configuration.RightCalibrationInfo.Transformation.ZoomFactor * RightImage.Width) / 2) + Configuration.RightCalibrationInfo.Transformation.LeftShift), (((Configuration.RightCalibrationInfo.Transformation.ZoomFactor * RightImage.Height) / 2) + Configuration.RightCalibrationInfo.Transformation.TopShift));
            g2.RotateTransform(Configuration.RightCalibrationInfo.Transformation.RotationAngle);
            g2.TranslateTransform(-1 * (((Configuration.RightCalibrationInfo.Transformation.ZoomFactor * RightImage.Width) / 2) + Configuration.RightCalibrationInfo.Transformation.LeftShift), -1 * (((Configuration.RightCalibrationInfo.Transformation.ZoomFactor * RightImage.Height) / 2) + Configuration.RightCalibrationInfo.Transformation.TopShift));
            g2.DrawImage(RightImage, new Rectangle(Configuration.RightCalibrationInfo.Transformation.LeftShift, Configuration.RightCalibrationInfo.Transformation.TopShift, (int)(RightImage.Width * Configuration.RightCalibrationInfo.Transformation.ZoomFactor), (int)(RightImage.Height * Configuration.RightCalibrationInfo.Transformation.ZoomFactor)), new Rectangle(0, 0, RightImage.Width, RightImage.Height), GraphicsUnit.Pixel);
            g2.Flush();
            g2.Dispose();
            //Transformation complete


            UnsafeBitmap OutputBitmap = new UnsafeBitmap(Configuration.ViewPort.Width, Configuration.ViewPort.Height);
            UnsafeBitmap LeftUnsafe;
            UnsafeBitmap RightUnsafe;
            if (!Configuration.IsSwap)
            {
                LeftUnsafe = new UnsafeBitmap(LeftBmp);
                RightUnsafe = new UnsafeBitmap(RightBmp);
            }
            else
            {
                LeftUnsafe = new UnsafeBitmap(RightImage);
                RightUnsafe = new UnsafeBitmap(LeftBmp);
            }

            OutputBitmap.LockBitmap();
            LeftUnsafe.LockBitmap();
            RightUnsafe.LockBitmap();

            for (int w = Configuration.ViewPort.Left; w < Configuration.ViewPort.Left + Configuration.ViewPort.Width; w++)
            {
                for (int h = Configuration.ViewPort.Top; h < Configuration.ViewPort.Top + Configuration.ViewPort.Height; h++)
                {
                    Pixel LeftPixel = LeftUnsafe.GetPixel(w, h);
                    Pixel RightPixel = RightUnsafe.GetPixel(w, h);
                    //Calculate Red
                    byte Red = (byte)((float)RightPixel.red * Configuration.RightCalibrationInfo.ColourFilter.Red + ((float)LeftPixel.red * Configuration.LeftCalibrationInfo.ColourFilter.Red));
                    //Calculate Green
                    byte Green = (byte)((float)RightPixel.green * Configuration.RightCalibrationInfo.ColourFilter.Green + ((float)LeftPixel.green * Configuration.LeftCalibrationInfo.ColourFilter.Green));
                    //Calculate Blue
                    byte Blue = (byte)((float)RightPixel.blue * Configuration.RightCalibrationInfo.ColourFilter.Blue + ((float)LeftPixel.blue * Configuration.LeftCalibrationInfo.ColourFilter.Blue));
                    //Set Pixel
                    OutputBitmap.SetPixel(w - Configuration.ViewPort.Left, h - Configuration.ViewPort.Top, new Pixel(Red, Green, Blue));
                }
            }

            OutputBitmap.UnlockBitmap();
            LeftUnsafe.UnlockBitmap();
            RightUnsafe.UnlockBitmap();

            LeftBmp.Dispose();
            RightBmp.Dispose();

            return OutputBitmap.Bitmap;
        }
    
    }
}
